﻿#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>

//1.一维数组题目

//sizeof(a)计算的是整个数组的大小
//&a,取出的是整个数组的地址
//除上面两种特殊情况，其他的a都是数组名，表示首元素的地址

/*
int main()
{
	int a[] = { 1,2,3,4 };


	printf("%d\n", sizeof(a));			//16		 sizeof(数组名)求的是整个数组的大小,4*4=16
	printf("%d\n", sizeof(a + 0));		//4/8		 a是首元素的地址，地址+0还是地址 4/8         a<==>&a[0] ；a+0<==>&a[0]+0 ,
	printf("%d\n", sizeof(*a));			//4			 a是首元素的地址，*a就是第一个元素1，元素是int类型     	*a <==> *&a[0] <= = > a[0]
	printf("%d\n", sizeof(a + 1));		//4/8		 a是首元素的地址，a+1，指向第二个元素，是第二个元素的地址
	printf("%d\n", sizeof(a[1]));		//4          a[1]表示数组第二个元素，元素是int类型
	printf("%d\n", sizeof(&a));			//4/8         &a取出的是整个数组的地址，数组的地址，也是地址
	printf("%d\n", sizeof(*&a));		//16         &a取出的是整个数组的地址，对数组的地址解引用，得到的是整个数组，*&相互抵消，不起作用
	printf("%d\n", sizeof(&a + 1));		//4/8		 &a取出的是整个数组的地址，+1，跳过这个数组的大小，指向下一个地址，还是地址
	printf("%d\n", sizeof(&a[0]));      //4/8        a[0]第一个元素，对第一个元素取地址，是地址，
	printf("%d\n", sizeof(&a[0] + 1));  //4/8		 a[0]第一个元素，对第一个元素取地址，地址+1,指向第二个元素，还是地址

	return 0;
}
*/

//2.字符数组

//sizeof计算的是所占内存的大小，
//strlen计算的是\0之前，所有字符的个数，\0不算

/*
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };    //6 
	printf("%d\n", sizeof(arr));         //6		sizeof(arr)  求的是整个数组的大小 6*1=6
	printf("%d\n", sizeof(arr + 0));	 //4/8      arr数组名是首元素的地址，+0还是首元素的地址 地址4/8
	printf("%d\n", sizeof(*arr));        //1		arr数组名是首元素的地址，首元素地址解引用，是首元素，
	printf("%d\n", sizeof(arr[1]));      //1		是第二元素的大小
	printf("%d\n", sizeof(&arr));        //4/8		&arr是取出来整个数组的地址，是地址就是4/8
	printf("%d\n", sizeof(&arr + 1));    //4/8		&arr是取出来整个数组的地址，+1，跳过整个数组，指向下一个元素，还是地址
	printf("%d\n", sizeof(&arr[0] + 1)); //4/8      arr[0]是首元素，&arr[0]是取出首元素的地址，地址+1，指向下一个元素，还是地址
	printf("%d\n", sizeof(arr[0] + 1));   //4       arr[0]是首元素字符 ’a’+1，发生整型提升，最后还是int类型

	printf("%d\n", strlen(arr));           //随机值  arr是数组名，是首元素的地址，strlen只要知道首元素的地址，就会往下读，至到道读到\0，求的是\0之前所有字符之和，这个数组里没有\0
	printf("%d\n", strlen(arr + 0));	   //随机值  arr是数组名，是首元素的地址，+0还是首元素的地址，没有\0

	printf("%d\n", strlen(*arr));		   //报错	 arr是数组名，是首元素的地址,首元素的地址解引用，得到首元素 'a'  strlen(‘a’)-->strlen(97) 非法访问，是野指针
	printf("%d\n", strlen(arr[1]));		   //报错    strlen(‘b’)-->strlen(98) 非法访问，是野指针 

	printf("%d\n", strlen(&arr));		   //随机值  &arr是取出来整个数组的地址，没有\0
	printf("%d\n", strlen(&arr + 1));	   //随机值  &arr是取出来整个数组的地址，跳过这个数组，找\0，也是随机的
	printf("%d\n", strlen(&arr[0] + 1));   //随机值  &arr[0]是取出来整个首元素地址，+1跳过一个元素，指向第二个元素，没有\0，随机的

	return 0;
}
*/


/*
int main()
{
	char arr[] = "abcdef";     //7    这里是7个元素，隐藏了一个\0
	printf("%d\n", sizeof(arr));       //7			这里是7个元素，隐藏了一个\0
	printf("%d\n", sizeof(arr + 0));   //4/8		arr数组名是首元素的地址，+0还是首元素的地址，地址的大小4/8
	printf("%d\n", sizeof(*arr));	   //1			arr数组名是首元素的地址，*arr就是首元素 'a'  1个字符1个字节
	printf("%d\n", sizeof(arr[1]));    //1			第二个字符b，1个字符 1个字节
	printf("%d\n", sizeof(&arr));      //4/8		&arr是取出来整个数组的地址，数组的地址也是地址
	printf("%d\n", sizeof(&arr + 1));  //4/8		&arr是取出来整个数组的地址，+1,跳过整个数组，指向下一个地址，还是地址
	printf("%d\n", sizeof(&arr[0] + 1));//4/8       &arr[0]是取出来整个首元素地址，+1跳过一个元素，指向第二个元素b 就是b的地址
	printf("%d\n", strlen(arr));       // 6         arr是首元素的地址，strlen只要至到地址，就一直读，至到读到\0为止
	printf("%d\n", strlen(arr + 0));   // 6         arr是首元素的地址，+0还是首元素地址，开始读取的位置不变
	printf("%d\n", strlen(*arr));      //报错       arr是首元素的地址，*arr，得到的是首元素，字符'a'  strlen('a')-->strlen(97)-野指针
	printf("%d\n", strlen(arr[1]));    //报错       arr[1]得到的是第二个元素，字符'b'  strlen('b')-->strlen(98)-野指针
	printf("%d\n", strlen(&arr));      //6          &arr是取出来整个数组的地址,也是从a开始读取
	printf("%d\n", strlen(&arr + 1));  //随机       &arr是取出来整个数组的地址,+1，跳过这个数组，从\0的下一位开始读取
	printf("%d\n", strlen(&arr[0] + 1)); //5        &arr[0]是取出来整个首元素地址，+1跳过一个元素，指向第二个元素，从b开始读取

	return 0;
}
*/


/*
int main()
{
	char* p = "abcdef";    // a b c d e f \0   p是指针，指向a
	printf("%d\n", sizeof(p));           //4/8		  p是指针，指向a,指针的地址，地址的大小是4/8
	printf("%d\n", sizeof(p + 1));		 //4/8        p+1也是指针，指向b，指针的地址，地址的大小是4/8
	printf("%d\n", sizeof(*p));			 //1		  p指向a, *p解引用,得到a，1个字符1字节
	printf("%d\n", sizeof(p[0]));		 //1		  p[0]<==>*(p+0)  得到a，1个字符1字节
	printf("%d\n", sizeof(&p));			 //4/8		  &p,p是一级指针变量，&p是二级指针，也是地址，
	printf("%d\n", sizeof(&p + 1));      //4/8		  &p是二级指针，+1,也是地址，
	printf("%d\n", sizeof(&p[0] + 1));   //4/8        p[0]<==>*(p+0)  得到a，&a+1,得到b的地址，也是地址


	printf("%d\n", strlen(p));            //6			p里放的是a的地址，从a开始算，找\0
	printf("%d\n", strlen(p + 1));		  //5			+1,指向b，从b开始数，找\0
	printf("%d\n", strlen(*p));           //报错err		p是a的地址，*p是a,  strlen('a')
	printf("%d\n", strlen(p[0]));		  //报错err		p[0]<==>*(p+0)<==>*p  *p是a,  strlen('a')
	printf("%d\n", strlen(&p));           //随机       p是单独开辟的一块空间，存的是a的地址，&p与字符串就无关了，什么时候找到\0就不一定了
	printf("%d\n", strlen(&p + 1));		  //随机       &p与字符串就无关了，+1，跳过这个地址，往后算什么时候找到\0就不一定了
	printf("%d\n", strlen(&p[0] + 1));    //5
	return 0;

}
*/


//3.二维数组

/*
int main()
{
	int a[3][4] = { 0 };  
	printf("%d\n", sizeof(a));          //48    整个数组的大小 3*4*4=48
	printf("%d\n", sizeof(a[0][0]));    //4     这是第一行第一列的那个元素，int  4
	printf("%d\n", sizeof(a[0]));       //16	a[0]是第一行这个一维数组的数组名，单独放在sizeof内部，a[0]表示第一个整个这个一维数组  sizeof(a[0])计算的就是第一行的大小  4*4=16
	printf("%d\n", sizeof(a[0] + 1));   //4/8   a[0]不是单独放在sizeof内部了，也没&，所以它就不再是第一行整个数组，a[0]不再代表第一行整个数组了，a[0]是首元素的地址，即a[0][0]的地址
	printf("%d\n", sizeof(*(a[0] + 1))); //4   (a[0] + 1)第一行第二个元素的地址，解引用，就是第一行第二个元素 int类型 4
	printf("%d\n", sizeof(a + 1));       //4/8   a数组名是首元素的地址，是第一行的地址，+1，是第二行的地址  
	printf("%d\n", sizeof(*(a + 1)));    //16    +1，是第二行的地址，解引用，是第二行一维数组4*4=16
	printf("%d\n", sizeof(&a[0] + 1));   //4/8	 a[0]是第一行的数组名，&a[0]是对数组名取地址，取的是整个第一行地址，+1是第二行的地址。
	printf("%d\n", sizeof(*(&a[0] + 1)));//16    (&a[0] + 1)第二行的地址，是一维数组的地址，解引用，就是一维数组 4*4=16
	printf("%d\n", sizeof(*a));          //16     a是数组名，数组名是首元素的地址，就是第一行的地址，*是第一行一维数组
	printf("%d\n", sizeof(a[3]));        //16     没有访问第四行，只是拿到了第四行的数组名，同sizeof(a[0])是一致的。

	return 0;
}
*/


//笔试题

//1.程序的结果是什么？

/*
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}
*/

//解析：
//&a取出整个数组的地址，+1，跳过这个数组，指向下一个元素
//&a取出的是数组的地址，应该放在数组指针里，它的类型是数组指针类型 int(*)[5]
//&a+1，跳过1个5个整型的数组,它的类型还是数组指针类型 int(*)[5]  
//把（&a+1)赋给ptr，两边类型不一样，要进行强制类型转换
//强制类型转换，只是类型发生了转换，但是值没有变 
//所以ptr 里放的还是（&a+1)的地址  它的类型是int *
// 
//如果（&a+1)赋给ptr，不进行强制类型转换，编译器会报警告，但是依旧会把（&a+1)的地址 赋给ptr
//ptr+-运算的时候，是取决于ptr本身的类型，跟他接收的类型无关
// 
//强制类型转换为int* ——>ptr指向数组末尾的下一个元素
//
//（a+1)，这里的a是首元素1的地址，+1，跳过1个元素，指向第二个元素，——指向2
//*(a + 1)——2
//*(ptr - 1)——5
// 2 5 


//2.程序的结果是什么？  

/*
//由于还没学习结构体，这里告知结构体的大小是20个字节
//假设p 的值为0x100000。 如下表表达式的值分别为多少？
//已知，结构体Test类型的变量大小是20个字节


struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}
*/

//解析：

//题目的条件是x86的环境下，地址是4个字节   
//结构体中char*是指针，只有再x86环境下，结构体的字节才是20，x64就不一定了

//p是结构体指针变量，它未来是有能力指向结构体对象的
//p的值为0x100000，相当于p已经进行初始化了， 结构体可以写成如下形式：
//struct Test
//{
//	int Num;
//	char* pcName;
//	short sDate;
//	char cha[2];
//	short sBa[4];
//}*p=(struct Test*) 0x100000;

//0x100000是十六进制，十六进制也是整数 
//p是结构体指针类型，右边是整型类型，有类型的差异 要强制类型转换 
//把0x100000数字值当成一个地址强制类型转换后赋给p

//p + 0x1
//0x1是十六进制的1，也就是1，也就是p+1
//一个整型指针类型+1，跳过1个整型字节
//一个字符指针类型+1,跳过1个字符字节
//一个结构体struct指针类型+1，跳过1个结构字节
//只有指针变量才探讨+1,跳过几个字节，变量+1，那就是数值+1
//
//p+0x1 == 0x100000+1  加1跳过一个结构体的大小：20个字节 0x100000+20  
//20要写成16进制，才能与0x100000相加
//14(20的16进制)  p=0x100014
//0x100000+0x100014==0x100014
//0x100014

//(unsigned long)p + 0x1
//unsigned long 无符号长整型 整数 0x100000(16进制)—— > 1, 048, 576(十进制)
//整型+1，就是值+1，1,048,576+1-->1,048,577 ，再还原成16进制0x100001
//整型变量+1,那就是+1，只有指针变量+-才探讨跳过几个字节
//0x100001

//(unsigned int*)p + 0x1
//无符号整型指针，整型指针+1就是跳过一个无符号整型的大小，4个字节
//0x100000+4==0x100004
//0x100004

//00000014
//00000001
//00000004
//这个题考的是，整型+1、结构体指针+1、整型指针+1的区别


//3.程序的结果是什么？   

/*
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);
	return 0;
}
*/

//解析：

//*ptr2
//a是数组名，表示首元素的地址，把a强转为int整型，整型+1，那就是+1
//a是个地址，假设地址是0x0012ff40
//a+1——>0x0012ff40+4——>0x0012ff44			a是数组名，是首元素的地址，相当于指针，整型指针+1,跳过4个字节
//(int)a+1——>0x0012ff40+1——>0x0011ff41		就是+1个字节
//强转整型之后再强转为指针，它又变成地址了，变成指针了，变成了a的第一个元素的4个字节里的第二个字节
//1 2 3 4 在内存中的存储
//低地址————————>高地址    小端存储模式
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 
//	 ⬆											 ⬆
//   ptr2现在指向的是这个字节，					 ptr1现在指向的是这个位置	
//ptr2是整形指针，它解引用，向后访问4个字节1个整型。它访问的是 00 00 00 02
//拿出来的值是 02 00 00 00 
//%x 打印16进制的时候，02的0 是不会打印出来的，所以结果是2000000


//ptr1[-1]
//&a取出整个数组的地址，类型是数值指针类型，+1，跳过这个数组，指向下一个元素，
//&a + 1类型依旧是数值指针类型，给强制类型转换为int*
//将(&a + 1)的地址赋值给ptr1;
//ptr1[-1]<==> *(ptr1-1)  得到结果是4
//ptr1是整形指针，所以向前退4个字节 是04 00 00 00  小端存储，
//所以拿出来的就是00 00 00 04   
//0不会打印，所以打印出来是4

//% x是按照16进制打印无符号整型
//4,2000000

//4.程序的结果是什么？     

/*
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}
*/

//解析：
//数组的元素是1，3，5，0，0，0
//a[0]是第一行的数组名，数组名表示首元素的地址，表示第一行第一个元素的地址
//p指向第一行第一个元素
//p[0]<==>*(p+0)<==>*p   得到第一行第一个元素 1

//
//如果是&数组名，那取出来的才是整个数组的地址，才是第一行一维数组的地址。
//如果没有&数组名，数组名也没有单独放在sizeof内部，那数组名就是首元素的地址，就是第一行第一个元素的地址。
//a[0]<==>&a[0][0]

//1


//5.程序的结果是什么？    

/*
int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}
*/

//解析：
//画图作答
//a[5][5] 数组在内存中是连续存放的，把五行放在同一行上 
//int(*p)[4]; 是数组指针，数组4个元素， p认为p指向的数组是4个整型的
//a是数组名，是二维数组首元素的地址，是第一行一维数组的地址，所以a是数组指针，类型是int(*)[5]
//a——>int(*)[4]
//p——>int(*)[5]
//p与a的类型是不同的，a赋给p，没有强制类型转换，是会报警告的，但是依旧会把a的内容第一行的地址赋给p
//p赋值后还是会一次跳过1个数组即4个整型，它认为它的类型还是int(*)[4]
// 
//p[4][2]=*(*(p+4)+2)
//p 一次跳过4个整形元素，跳4次 4*4=16  就是a[3][1] +2  就是a[3][3]
//两个指针相减，得到的是两个指针之间的元素个数 
// a[3][3]-a[4][2] =-4   随这数组下标的增长，地址是从低到高变化的，所以是-4  
//%d 打印整型 是-4
//%p 打印地址  地址是没有原反补的概念的，%p认为内存里的值就是地址  内存里存的补码
//10000000 00000000 00000000 00000100 -4的原码
//11111111 11111111 11111111 11111011 -4的反码
//11111111 11111111 11111111 11111100 -4的补码

//1111 1111 1111 1111 1111 1111 1111 1100 
//	f	f	 f		f	f	 f	 f	  c 
// ff ff ff fc
// 

//不同类型的指针，+1跳过的字节数是不同的。
//FFFFFFFC,-4

//6.程序的结果是什么？    

/*
int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}
*/

//解析：(&aa + 1) &aa取出整个数组的地址，+1，跳过整个数组，指向数组末尾下一个元素，
//&aa数组的地址，放在数组指针里int(*)[2][5]里，+1,它的类型还是数组指针类型int(*)[2][5]
//所以需要强转int*
//(*(aa + 1) aa是数组名，是首元素的地址，是第一行的地址，+1，跳过这一行，指向下一行，即指向元素6
// *(aa + 1)<==>aa[1] aa[1]是数组aa的第二行的数组名，aa[1]代表的就是第二行第一个元素的地址，即6的地址  
// 6的地址 指针本身的类型就是int*, 前面还进行了强转，这是没必要的。
//*(ptr1 - 1)——10
//*(ptr2 - 1)——5
//10  5


//7.程序的结果是什么？    

//int main()
//{
//	char* a[] = { "work","at","alibaba" };
//	char** pa = a;
//	pa++;
//	printf("%s\n", *pa);
//	return 0;
//}

//解析：a是一个数组，有3个元素，每个元素的类型是char*,是一级指针，分别指向 "work","at","alibaba" 
//char* ——w
//char* ——a
//char* ——a
//a是数组名，是首元素的地址，首元素是一级指针，那就是一级指针的地址，是二级指针，所以pa是char**
//pa指向a[0]， a[0]的元素是work
// 
//int * p ;  p+1 :  *说明p是指针，int说明p指向的是int类型。
//所以：p + 1要跳过int类型大小的字节
//char** p;  p + 1:  char** p  后面那个 * 说明p是指针，前面的char * 说明p指向的是char * 类型。
//所以：p + 1要跳过char * 类型大小的字节
//所以pa++ <==> pa=pa+1 要跳过一个char*类型的
// 
//pa++,跳过一个char*类型大小的字节，指向下一个char*类型——a[1]
//a[1]解引用是at
//at


//8.程序的结果是什么？    

/*
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);			//POINT
	printf("%s\n", *-- * ++cpp + 3);	//ER
	printf("%s\n", *cpp[-2] + 3);		//ST
	printf("%s\n", cpp[-1][-1] + 1);	//EW
	return 0;
}

*/

//解析：
//前置后置++是有副作用的，会改变变量本身的值
//画图分析即可